package com.sls.mall.product.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.sls.mall.common.utils.R;
import com.sls.mall.product.service.CategoryBrandRelationService;
import com.sls.mall.product.vo.Catelog2VO;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sls.mall.common.utils.PageUtils;
import com.sls.mall.common.utils.Query;

import com.sls.mall.product.dao.CategoryDao;
import com.sls.mall.product.entity.CategoryEntity;
import com.sls.mall.product.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {


    @Autowired
    CategoryService categoryService;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;



    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {

        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);

        List<CategoryEntity> collect = categoryEntityList.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu) -> {
            menu.setChildren(getChildrens(menu, categoryEntityList));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

    public List<CategoryEntity> getChildrens(CategoryEntity entty,List<CategoryEntity> all)
    {
        List<CategoryEntity> collect = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == entty.getCatId();
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {

        List<Long> path = new ArrayList<>();

        List<Long> parentPath = findParentPath(catelogId,path);

        return parentPath.toArray(new Long[parentPath.size()]);

    }

    private List<Long> findParentPath(Long catelogId,List<Long> path) {

        path.add(catelogId);

        CategoryEntity categoryEntity = this.getById(catelogId);

        if (categoryEntity.getParentCid() != 0)
        {
            findParentPath(categoryEntity.getParentCid(),path);
        }

        Collections.reverse(path);
        return path;
    }

    @Override
    public void updateCascade(CategoryEntity category) {

        categoryService.updateCascade(category);

        if(category.getCatId()!=null)
        {
            categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }

        //TODO 以后其他 级联表 冗余字段也要修改
    }

    @Override
    public List<CategoryEntity> getLevel1Categorys() {

        List<CategoryEntity> categoryEntityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));

        return categoryEntityList;

    }


    /**
     *  1. springboot 2.0 以后默认使用 lettuce 作为操作 redis 的客户端。它使用 netty 进行网络通信。
     *  2. lettuce 的 bug 导致 netty 对外内存溢出 -Xmx 300m ； netty 如果没有指定堆外内存，默认使用 -Xmx 300m
     *      可以通过 -Dio.netty.maxDirectMemory 调大堆外内存
     *
     *  解决方案：不能 只去调大堆外内存；时间累积还是会 OODM
     *  1，升级 lettuce     2. 切换使用 jedis
     *
     * @return
     */
    @Override
    public Map<Long, List<Catelog2VO>> getCatalogJson() {

        // 1. 加入缓存逻辑，缓存中存的数据是 json 字符串

        // 2. 缓存中没有，查询数据库
        // json 夸语言，跨平台兼容。
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON))
        {
            // 2.1 缓存中没有，查询数据库
//            Map<Long, List<Catelog2VO>> catalogJson = getCatalogJsonFormDB();

            Map<Long, List<Catelog2VO>> catalogJson = getCatalogJsonFormDBWithRedisson();

//            if (catalogJSON != null)
//            {
//                // 2.2 查到了在放到缓存中，将对象转为 json 放入缓存中
//                String s = JSON.toJSONString(catalogJsonFormDB);
//
//                // 2.3 随机设置过期时间 避免 缓存 雪崩
//                int i = RandomUtil.randomInt(1, 10);
//                stringRedisTemplate.opsForValue().set("catalogJSON",s,i,TimeUnit.SECONDS);
//            }else{
//                // 2.4 如果查询为空值，短暂缓存 避免  缓存 穿透
//                stringRedisTemplate.opsForValue().set("catalogJSON",null,5, TimeUnit.SECONDS);
//            }

            return catalogJson;
        }

        // 转为我们制定的对象
        Map<Long, List<Catelog2VO>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<Long, List<Catelog2VO>>>() {});

        return result;

    }


    /**
     *   分布式锁
     * @return
     */
    public Map<Long, List<Catelog2VO>> getCatalogJsonFormDBWithRedisson() {

        RLock lock = redissonClient.getLock("prodcut-catalog-lock");
        try {

            // 加锁并 设置超过业务时间的  过期时间
            lock.lock(15,TimeUnit.SECONDS);

            Map<Long, List<Catelog2VO>> catalogJsonFormDB = getCatalogJsonFormDB();

            return catalogJsonFormDB;

        }finally {

            // 1. 是否加锁           2. 解锁的锁，是否是当前线程加的锁   不加的话 高并发下 会有问题
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     *   传统 synchronized 锁
     * @return
     */
    public Map<Long, List<Catelog2VO>> getCatalogJsonFormDBWithynchronized() {


        synchronized (this) {

            Map<Long, List<Catelog2VO>> catalogJsonFormDB = getCatalogJsonFormDB();

            return catalogJsonFormDB;
        }

    }


    public Map<Long, List<Catelog2VO>> getCatalogJsonFormDB() {


            /**
             *   1. 将数据库的多次查询变为一次查询
             */
            List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);

            // 1. 查出所有一级分类
            List<CategoryEntity> level1Categorys = getParent_cid(categoryEntityList, 0L);

            // 2. 封装数据
            Map<Long, List<Catelog2VO>> parent_id = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId(), v -> {
                // 2.1 每一个的一级分类，查到这个一级分类的二级分类
                List<CategoryEntity> categoryEntities = getParent_cid(categoryEntityList, v.getCatId());

                // 2.2 封装上面的结果
                List<Catelog2VO> catelog2VOs = null;
                if (categoryEntities != null) {
                    catelog2VOs = categoryEntities.stream().map(l2 -> {
                        Catelog2VO catelog2VO = new Catelog2VO(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                        // 3.1 找当前二级分类的三级分类封装成 vo
//                    List<CategoryEntity> level3Categorys = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId().toString()));

                        List<CategoryEntity> level3Categorys = getParent_cid(categoryEntityList, l2.getCatId());

                        if (level3Categorys != null) {
                            List<Catelog2VO.Catelog3VO> collect = level3Categorys.stream().map(l3 -> {
                                Catelog2VO.Catelog3VO catelog3VO = new Catelog2VO.Catelog3VO(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                                return catelog3VO;
                            }).collect(Collectors.toList());
                            catelog2VO.setCatalog3List(collect);
                        }


                        return catelog2VO;
                    }).collect(Collectors.toList());
                }
                return catelog2VOs;

            }));

            // 4.结果放入缓存
            if (parent_id != null) {
                // 4.1 查到了在放到缓存中，将对象转为 json 放入缓存中
                String s = JSON.toJSONString(parent_id);

                // 4.2 随机设置过期时间 避免 缓存 雪崩
                int i = RandomUtil.randomInt(1, 10);
                stringRedisTemplate.opsForValue().set("catalogJSON", s, i, TimeUnit.SECONDS);
            } else {
                // 4.3 如果查询为空值，短暂缓存 避免  缓存 穿透
                stringRedisTemplate.opsForValue().set("catalogJSON", null, 5, TimeUnit.SECONDS);
            }


            return parent_id;

    }

    public List<CategoryEntity> getParent_cid( List<CategoryEntity> categoryEntityList,Long parent_cid) {
//        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));

        List<CategoryEntity> collect = categoryEntityList.stream().filter(item -> item.getParentCid() == parent_cid).collect(Collectors.toList());

        return collect;

    }
}